package com.eksframework.commons.core.converter;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.Objects;

/**
 * BigDecimal转换器
 *
 * @author: chenfenghai
 * @version: 1.0
 * @date: 2020-04-16 16:20
 * @blame Team
 */
public class BigDecimalConverter<T> extends AbstractNumberConverter<T> {

    /** BigDecimal对象 */
    private BigDecimal bigDecimal;

    //-----------------------------------------------------------------------------------------------------------------
    /**
     * Constructs an instance with the value present.
     *
     * @param object convert value
     */
    public BigDecimalConverter(final T object) {
        super(object);
    }

    //-----------------------------------------------------------------------------------------------------------------
    /**
     * * 创建实例
     *
     * @param object 输入参数
     * @return not null
     */
    @Nonnull
    public static <T> BigDecimalConverter of(@Nullable final T object) {
        return new BigDecimalConverter<>(object);
    }

    //-----------------------------------------------------------------------------------------------------------------
    /**
     * get rounding mode to apply
     *
     * @param roundingMode rounding mode
     * @return RoundingMode
     * @see RoundingMode
     */
    @Override
    RoundingMode getRoundingMode(RoundingMode roundingMode) {
        return roundingMode == null ? RoundingMode.HALF_UP : roundingMode;
    }

    //-----------------------------------------------------------------------------------------------------------------
    /**
     * <p>Convert a <code>Object</code> to a <code>BigDecimal</code>
     *
     * @param value {@code Object} to convert to a {@code BigDecimal}.
     * @return BigDecimal
     * @throws NullPointerException  – if the value is null.
     * @throws NumberFormatException if the value is not a valid representation of a {@code BigDecimal}.
     */
    private BigDecimal convert(final Object value) {
        Objects.requireNonNull(value);
        if (bigDecimal != null) {
            return bigDecimal;
        } else if (Byte.class.equals(value.getClass())) {
            this.bigDecimal = BigDecimal.valueOf(((Byte) value).longValue());
        } else if (Short.class.equals(value.getClass())) {
            this.bigDecimal = BigDecimal.valueOf(((Short) value).longValue());
        } else if (Integer.class.equals(value.getClass())) {
            this.bigDecimal = BigDecimal.valueOf(((Integer) value).longValue());
        } else if (Long.class.equals(value.getClass())) {
            this.bigDecimal = BigDecimal.valueOf((Long) value);
        } else if (Float.class.equals(value.getClass())) {
            this.bigDecimal = BigDecimal.valueOf(((Float) value).doubleValue());
        } else if (Double.class.equals(value.getClass())) {
            this.bigDecimal = BigDecimal.valueOf((Double) value);
        } else if (Character.class.equals(value.getClass())) {
            this.bigDecimal = new BigDecimal(new char[]{(Character) value});
        } else if (Boolean.class.equals(value.getClass())) {
            this.bigDecimal = Boolean.parseBoolean(value.toString()) ? BigDecimal.ONE : BigDecimal.ZERO;
        } else if (BigInteger.class.equals(value.getClass())) {
            this.bigDecimal = new BigDecimal((BigInteger) value);
        } else if (BigDecimal.class.equals(value.getClass())) {
            this.bigDecimal = (BigDecimal) value;
        } else if (value instanceof char[]) {
            this.bigDecimal = new BigDecimal((char[]) value);
        } else if (value instanceof Number) {
            this.bigDecimal = BigDecimal.valueOf(((Number) value).doubleValue());
        } else if (value instanceof CharSequence) {
            this.bigDecimal = new BigDecimal(((CharSequence) value).toString());
        } else {
            this.bigDecimal = new BigDecimal(value.toString());
        }
        return bigDecimal;
    }

    //-----------------------------------------------------------------------------------------------------------------
    /**
     * <p>Convert a <code>Object</code> to a <code>BigDecimal</code>, returning
     * <code>BigDecimal.ZERO</code> if the conversion fails.</p>
     *
     * @return a {@code BigDecimal}, not null
     * @see #toBigDecimalDefault(BigDecimal)
     */
    @Nonnull
    public BigDecimal toBigDecimal() {
        return Objects.requireNonNull(toBigDecimalDefault(BigDecimal.ZERO));
    }

    /**
     * <p>Convert a <code>Object</code> to a <code>BigDecimal</code>, returning
     * <code>BigDecimal.ZERO</code> if the conversion fails.</p>
     *
     * @param scale scale of the {@code BigDecimal} value to be returned.
     * @return a {@code BigDecimal}, not null
     * @see #toBigDecimalDefault(BigDecimal, int)
     */
    @Nonnull
    public BigDecimal toBigDecimal(final int scale) {
        return Objects.requireNonNull(toBigDecimalDefault(BigDecimal.ZERO, scale));
    }

    /**
     * <p>Convert a <code>Object</code> to a <code>BigDecimal</code>, returning
     * <code>BigDecimal.ZERO</code> if the conversion fails.</p>
     *
     * @param scale        scale of the {@code BigDecimal} value to be returned.
     * @param roundingMode The rounding mode to apply
     * @return a {@code BigDecimal}, not null
     * @see #toBigDecimalDefault(BigDecimal, int, RoundingMode)
     */
    @Nonnull
    public BigDecimal toBigDecimal(final int scale, @Nullable final RoundingMode roundingMode) {
        return Objects.requireNonNull(toBigDecimalDefault(BigDecimal.ZERO, scale, roundingMode));
    }

    //-----------------------------------------------------------------------------------------------------------------
    /**
     * <p>Convert a <code>Object</code> to a <code>BigDecimal</code>, returning a
     * default value if the conversion fails.</p>
     *
     * @param defaultValue the default value
     * @return a {@code BigDecimal}, may be null
     * @see #convert(Object)
     */
    @Nullable
    public BigDecimal toBigDecimalDefault(@Nullable final BigDecimal defaultValue) {
        try {
            return super.object == null ? defaultValue : convert(super.object);
        } catch (Exception ex) {
            return defaultValue;
        }
    }

    /**
     * <p>Convert a <code>Object</code> to a <code>BigDecimal</code>, returning a
     * default value if the conversion fails.</p>
     *
     * @param defaultValue the default value
     * @param scale        scale of the {@code BigDecimal} value to be returned.
     * @return a {@code BigDecimal}, may be null
     * @see #toBigDecimalDefault(BigDecimal, int, RoundingMode)
     */
    @Nullable
    public BigDecimal toBigDecimalDefault(
            @Nullable final BigDecimal defaultValue, final int scale) {
        return toBigDecimalDefault(defaultValue, scale, null);
    }

    /**
     * <p>Convert a <code>Object</code> to a <code>BigDecimal</code>, returning a
     * default value if the conversion fails.</p>
     *
     * @param defaultValue the default value
     * @param scale        scale of the {@code BigDecimal} value to be returned.
     * @param roundingMode The rounding mode to apply, null for default RoundingMode.HALF_UP
     * @return a {@code BigDecimal}, may be null
     * @see #toBigDecimalDefault(BigDecimal)
     * @see BigDecimal#setScale(int, RoundingMode)
     */
    @Nullable
    public BigDecimal toBigDecimalDefault(
            @Nullable final BigDecimal defaultValue,
            final int scale, @Nullable final RoundingMode roundingMode) {
        final BigDecimal nullableBigDecimal = toBigDecimalDefault(defaultValue);
        final RoundingMode lastRoundingMode = getRoundingMode(roundingMode);
        return nullableBigDecimal == null ? defaultValue : nullableBigDecimal.setScale(scale, lastRoundingMode);
    }

    //-----------------------------------------------------------------------------------------------------------------
    /**
     * <p>Convert a <code>Object</code> to a <code>BigDecimal</code>
     *
     * @return a {@code BigDecimal}, not null
     * @throws NullPointerException  – if the value is null.
     * @throws NumberFormatException if the value is not a valid representation of a {@code BigDecimal}.
     * @see #convert(Object)
     */
    @Nonnull
    public BigDecimal toBigDecimalThrow() {
        return convert(super.object);
    }

    /**
     * <p>Convert a <code>Object</code> to a <code>BigDecimal</code>
     *
     * @param scale scale of the {@code BigDecimal} value to be returned.
     * @return a {@code BigDecimal}, not null
     * @throws NullPointerException  – if the value is null.
     * @throws NumberFormatException if the value is not a valid representation of a {@code BigDecimal}.
     * @see #toBigDecimalThrow(int, RoundingMode)
     */
    @Nonnull
    public BigDecimal toBigDecimalThrow(final int scale) {
        return toBigDecimalThrow(scale, null);
    }

    /**
     * <p>Convert a <code>Object</code> to a <code>BigDecimal</code>
     *
     * @param scale        scale of the {@code BigDecimal} value to be returned.
     * @param roundingMode The rounding mode to apply, null for default RoundingMode.HALF_UP
     * @return a {@code BigDecimal}, not null
     * @throws NullPointerException  – if the value is null.
     * @throws NumberFormatException if the value is not a valid representation of a {@code BigDecimal}.
     * @throws ArithmeticException   if {@code roundingMode==UNNECESSARY}
     *                               and the specified scaling operation would require rounding.
     * @see RoundingMode
     * @see #convert(Object)
     * @see BigDecimal#setScale(int, RoundingMode)
     */
    @Nonnull
    public BigDecimal toBigDecimalThrow(final int scale, @Nullable final RoundingMode roundingMode) {
        final RoundingMode lastRoundingMode = getRoundingMode(roundingMode);
        return convert(super.object).setScale(scale, lastRoundingMode);
    }
}